package net.maergaiyun.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import net.maergaiyun.config.RabbitMQConfig;
import net.maergaiyun.constant.RedisKey;
import net.maergaiyun.controller.request.TrafficPageRequest;
import net.maergaiyun.controller.request.UseTrafficRequest;
import net.maergaiyun.enums.BizCodeEnum;
import net.maergaiyun.enums.EventMessageType;
import net.maergaiyun.enums.TaskStateEnum;
import net.maergaiyun.exception.BizException;
import net.maergaiyun.feign.ProductFeignService;
import net.maergaiyun.feign.ShortLinkFeignService;
import net.maergaiyun.interceptor.LoginInterceptor;
import net.maergaiyun.manager.TrafficManager;
import net.maergaiyun.manager.TrafficTaskManager;
import net.maergaiyun.model.EventMessage;
import net.maergaiyun.model.TrafficDO;
import net.maergaiyun.model.TrafficTaskDO;
import net.maergaiyun.service.TrafficService;
import net.maergaiyun.util.JsonData;
import net.maergaiyun.util.JsonUtil;
import net.maergaiyun.util.TimeUtil;
import net.maergaiyun.vo.ProductVO;
import net.maergaiyun.vo.TrafficVO;
import net.maergaiyun.vo.UseTrafficVO;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class TrafficServiceImpl implements TrafficService {
    @Autowired
    private TrafficManager trafficManager;

    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private TrafficTaskManager trafficTaskManager;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RabbitMQConfig rabbitMQConfig;

    @Autowired
    private ShortLinkFeignService shortLinkFeignService;

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void handleTrafficMessage(EventMessage eventMessage) {
        String eventMessageType = eventMessage.getEventMessageType();
        Long accountNo = eventMessage.getAccountNo();

        if (eventMessageType.equalsIgnoreCase(EventMessageType.PRODUCT_ORDER_PAY.name())) {
            // 订单已支付, 新增流量
            String content = eventMessage.getContent();

            Map<String, Object> orderInfoMap = JsonUtil.json2Obj(content, Map.class);
            String outTradeNo = (String) orderInfoMap.get("outTradeNo");
            Integer buyNum = (Integer) orderInfoMap.get("buyNum");
            String productStr = (String) orderInfoMap.get("product");
            ProductVO productVO = JsonUtil.json2Obj(productStr, ProductVO.class);
            log.info("商品信息: {}", productStr);

            // 流量包有效期
            LocalDateTime expiredDateTime = LocalDateTime.now().plusDays(productVO.getValidDay());
            Date date = Date.from(expiredDateTime.atZone(ZoneId.systemDefault()).toInstant());

            TrafficDO trafficDO = TrafficDO.builder().accountNo(accountNo).dayLimit(productVO.getDayTimes() * buyNum).dayUsed(0).totalLimit(productVO.getTotalTimes()).pluginType(productVO.getPluginType()).level(productVO.getLevel()).productId(productVO.getId()).outTradeNo(outTradeNo).expiredDate(date).build();

            int rows = trafficManager.add(trafficDO);
            log.info("新增付费流量包: rows={}, trafficDO={}", rows, trafficDO);
            // 新增流量包后, 删除每天可用流量包次数key
            String totalTrafficTimesKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
            redisTemplate.delete(totalTrafficTimesKey);

        } else if (eventMessageType.equalsIgnoreCase(EventMessageType.TRAFFIC_FREE_INIT.name())) {
            // 发放免费流量包
            Long productId = Long.valueOf(eventMessage.getBizId());
            JsonData jsonData = productFeignService.detail(productId);
            ProductVO productVO = jsonData.getData(new TypeReference<ProductVO>() {
            });
            TrafficDO trafficDO = TrafficDO.builder().accountNo(accountNo).dayLimit(productVO.getDayTimes()).dayUsed(0).totalLimit(productVO.getTotalTimes()).pluginType(productVO.getPluginType()).level(productVO.getLevel()).productId(productVO.getId()).outTradeNo("free_init").expiredDate(new Date()).build();
            int rows = trafficManager.add(trafficDO);
            log.info("新增免费流量包(新用户注册): rows={}, trafficDO={}", rows, trafficDO);
        } else if (eventMessageType.equalsIgnoreCase(EventMessageType.TRAFFIC_USED.name())) {
            Long trafficTaskId = Long.valueOf(eventMessage.getBizId());
            TrafficTaskDO trafficTaskDO = trafficTaskManager.findByIdAndAccountNo(trafficTaskId, accountNo);
            // 非空且锁定状态
            if (trafficTaskDO != null && trafficTaskDO.getLockState().equalsIgnoreCase(TaskStateEnum.LOCK.name())) {
                JsonData jsonData = shortLinkFeignService.check(trafficTaskDO.getBizId());
                if (jsonData.getCode() != 0) {
                    log.error("检查创建短链失败, 回滚流量包");
                    String trafficUseDateStr = TimeUtil.format(trafficTaskDO.getGmtModified(), "yyyy-MM-dd");
                    trafficManager.releaseUsedTimes(accountNo, trafficTaskDO.getTrafficId(), 1, trafficUseDateStr);
                    // 恢复流量包后删除缓存key
                    String totalTrafficTimesKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
                    redisTemplate.delete(totalTrafficTimesKey);
                }
                trafficTaskManager.deleteByIdAndAccountNo(trafficTaskId, accountNo);
            }
        }
    }

    @Override
    public Map<String, Object> pageAvailable(TrafficPageRequest request) {
        int page = request.getPage();
        int size = request.getSize();
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        IPage<TrafficDO> trafficDOIPage = trafficManager.pageAvailable(page, size, accountNo);
        List<TrafficDO> records = trafficDOIPage.getRecords();

        List<TrafficVO> trafficVOList = records.stream().map(obj -> beanProcess(obj)).collect(Collectors.toList());

        Map<String, Object> pageMap = new HashMap<>(3);
        pageMap.put("total", trafficDOIPage.getTotal());
        pageMap.put("page", trafficDOIPage.getPages());
        pageMap.put("data", trafficVOList);
        return pageMap;
    }

    @Override
    public TrafficVO detail(Long trafficId) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        TrafficDO trafficDO = trafficManager.findByIdAndAccountNo(trafficId, accountNo);
        TrafficVO trafficVO = beanProcess(trafficDO);
        return trafficVO;
    }

    @Override
    public boolean deleteExpireTraffic() {
        return trafficManager.deleteExpireTraffic();
    }

    /**
     * 扣减流量包可使用次数
     * <p>
     * 1、查询用户全部可用流量包
     * 2、遍历用户可用流量包
     * 2.1 判断今天是否更新(用修改日期判断)
     * (1)未更新的加入待更新集合
     * 总次数增加
     * (2)已更新的判断是否可用
     * 可用则总次数增加
     * 不可用则忽略
     * 3、更新用户今日流量包
     * 4、扣减要使用的流量包次数
     *
     * @param trafficRequest
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public JsonData reduce(UseTrafficRequest trafficRequest) {
        Long accountNo = trafficRequest.getAccountNo();
        UseTrafficVO useTrafficVO = processTrafficList(accountNo);
        log.info("今日可用流量包次数: {}", useTrafficVO.getDayTotalLeftTimes());
        log.info("当前使用流量包: {}", useTrafficVO.getCurrentTrafficDO());
        if (useTrafficVO.getCurrentTrafficDO() == null) {
            return JsonData.buildResult(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }

        if (!useTrafficVO.getUnUpdatedTrafficIds().isEmpty()) {
            int rows = trafficManager.batchUpdateUsedTimes(accountNo, useTrafficVO.getUnUpdatedTrafficIds());
            log.info("更新未更新的流量包条数: rows={}", rows);
        }

        int rows = trafficManager.addDayUsedTimes(accountNo, useTrafficVO.getCurrentTrafficDO().getId(), 1);

        TrafficTaskDO trafficTaskDO = TrafficTaskDO.builder().trafficId(useTrafficVO.getCurrentTrafficDO().getId()).accountNo(accountNo).useTimes(1).lockState(TaskStateEnum.LOCK.name()).bizId(trafficRequest.getBizNo()).build();
        trafficTaskManager.add(trafficTaskDO);

        EventMessage trafficUseEventMessage = EventMessage.builder().accountNo(accountNo).bizId(trafficTaskDO.getId() + "").eventMessageType(EventMessageType.TRAFFIC_USED.name()).build();
        // 发送延迟消息, 用于创建短链失败流量包回滚
        rabbitTemplate.convertAndSend(rabbitMQConfig.getTrafficEventExchange(), rabbitMQConfig.getTrafficReleaseDelayRoutingKey(), trafficUseEventMessage);

        if (rows != 1) {
            throw new BizException(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }

        // 往redis里设置总流量包次数, 在短链服务递减次数; 如果新增流量包, 删除该key
        long leftSeconds = TimeUtil.getRemainSecondsOneDay(new Date());
        String totalTrafficTimesKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
        redisTemplate.opsForValue().set(totalTrafficTimesKey, useTrafficVO.getDayTotalLeftTimes() - 1, leftSeconds, TimeUnit.SECONDS);

        return JsonData.buildSuccess();
    }

    private UseTrafficVO processTrafficList(Long accountNo) {
        List<TrafficDO> trafficDOList = trafficManager.selectAvailableTraffics(accountNo);
        if (trafficDOList == null || trafficDOList.isEmpty()) {
            throw new BizException(BizCodeEnum.TRAFFIC_EXCEPTION);
        }

        // 天剩余可用总次数
        int dayTotalLeftTimes = 0;
        // 当前使用的流量包
        TrafficDO currentTrafficDO = null;
        // 没过期且当天未更新的流量包id集合
        List<Long> unUpdatedTrafficIds = new ArrayList<>();

        String todayStr = TimeUtil.format(new Date(), "yyyy-MM-dd");
        for (TrafficDO trafficDO : trafficDOList) {
            String trafficUpdateDate = TimeUtil.format(trafficDO.getGmtModified(), "yyyy-MM-dd");
            if (trafficUpdateDate.equalsIgnoreCase(todayStr)) {
                // 已更新
                int dayLeftTimes = trafficDO.getDayLimit() - trafficDO.getDayUsed();
                dayTotalLeftTimes += dayLeftTimes;

                if (dayLeftTimes > 0 && currentTrafficDO == null) {
                    currentTrafficDO = trafficDO;
                }
            } else {
                // 未更新
                dayTotalLeftTimes += trafficDO.getDayLimit();
                unUpdatedTrafficIds.add(trafficDO.getId());

                if (currentTrafficDO == null) {
                    currentTrafficDO = trafficDO;
                }
            }
        }

        UseTrafficVO useTrafficVO = new UseTrafficVO(dayTotalLeftTimes, currentTrafficDO, unUpdatedTrafficIds);
        return useTrafficVO;
    }

    private TrafficVO beanProcess(TrafficDO trafficDO) {
        TrafficVO trafficVO = new TrafficVO();
        BeanUtils.copyProperties(trafficDO, trafficVO);

        // 惰性更新, 比较当天时间和修改时间, 如果不一致则说明未更新流量包记录
        String todayStr = TimeUtil.format(new Date(), "yyyy-MM-dd");
        String trafficUpdateStr = TimeUtil.format(trafficDO.getGmtModified(), "yyyy-MM-dd");
        if (!todayStr.equalsIgnoreCase(trafficUpdateStr)) {
            trafficVO.setDayUsed(0);
        }

        return trafficVO;
    }
}
